package br.mikhas.container.config;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import br.mikhas.container.application.ApplicationScope;
import br.mikhas.container.config.Dependency.DependencyTarget;
import br.mikhas.container.factory.CircularReferenceException;
import br.mikhas.util.Assert;

/**
 * A standard bean definition with most basic bean data
 * 
 * @author Mikhail Domanoski
 * 
 */
public class StandadBeanDefinition implements BeanDefinition {

	/**
	 * A list holding resource dependencies definition
	 */
	protected List<Dependency> dependencies = new LinkedList<Dependency>();

	/**
	 * The class which instantiate the resource
	 */
	protected Class<?> instantiatingClass;

	/**
	 * The type of the resource
	 */
	protected Class<?> type;

	/**
	 * The name of the resource
	 */
	protected String name;

	/**
	 * The name of the scope in which this resource has it life-cycle
	 */
	protected String scopeName = ApplicationScope.APPLICATION_SCOPE;

	/**
	 * Flags if the definition is on initialization phase
	 */
	protected transient boolean initializationPhase;

	/**
	 * The name of a method which requires no arguments that should be called
	 * defore the bean or context destruction.
	 */
	protected String destroyMethod;

	/**
	 * The name of a method which requires no arguments that should be called
	 * after the bean creation and property set.
	 */
	protected String initializingMethod;

	/**
	 * Creates a new {@link BeanDefinition} defining it's name and type.
	 * <p>
	 * It will be assumed that the class which instantiates the resource is the
	 * class which defines the resource type.
	 * 
	 * @param name
	 *            The name of the resource
	 * @param type
	 *            The type of the resource
	 */
	public StandadBeanDefinition(String name, Class<?> type) {
		this(name, type, type);
	}

	/**
	 * Creates a new {@link BeanDefinition} defining it's name, type and
	 * instantiating class.
	 * 
	 * @param name
	 *            The name of the resource
	 * @param type
	 *            The type of the resource
	 * @param instantiatingClass
	 *            The class which instantiate the resource
	 */
	public StandadBeanDefinition(String name, Class<?> type,
			Class<?> instantiatingClass) {

		Assert.notEmpty(name);
		Assert.notNull(type);
		Assert.notNull(instantiatingClass);

		this.name = name;
		this.instantiatingClass = instantiatingClass;
		this.type = type;
	}

	@Override
	public Dependency[] getDependencies() {
		Dependency[] dependencies = new Dependency[this.dependencies.size()];
		return this.dependencies.toArray(dependencies);
	}

	/**
	 * Adds a new dependency to this resource.
	 * 
	 * @param dependency
	 *            a dependency to this resource
	 */
	public void addDependency(Dependency dependency) {
		Assert.notNull(dependency);
		this.dependencies.add(dependency);
	}

	@Override
	public String getName() {
		return this.name;
	}

	@Override
	public String getScope() {
		return this.scopeName;
	}

	/**
	 * Sets the scope which this resource belongs.
	 * <p>
	 * By default, it's assumed that all resources belogs to application scope
	 * 
	 * @param scopeName
	 *            The name of the scope
	 */
	public void setScope(String scopeName) {
		Assert.notEmpty(scopeName);
		this.scopeName = scopeName;
	}

	@Override
	public String toString() {
		return "Resource [name=" + name + ", type=" + type
				+ ", instantiatingClass=" + this.instantiatingClass
				+ ", scopeName=" + scopeName + "]";
	}

	@Override
	public Class<?> getInstantiatingClass() {
		return this.instantiatingClass;
	}

	@Override
	public Class<?> getType() {
		return type;
	}

	@Override
	public Dependency[] getDependencies(DependencyTarget destiny) {
		Assert.notNull(destiny);

		List<Dependency> dependencies = new ArrayList<Dependency>(
				this.dependencies.size());

		for (Dependency dependency : this.dependencies) {
			if (dependency.getTarget() == destiny) {
				dependencies.add(dependency);
			}
		}
		Dependency[] dependencies2 = new Dependency[dependencies.size()];
		return dependencies.toArray(dependencies2);
	}

	/**
	 * Defines the state of the bean definition as initializing
	 */
	public void initialize() {
		if (this.initializationPhase)
			throw new CircularReferenceException(
					"Impossible to initialize bean. It's not on idle state");
		this.initializationPhase = true;
	}

	/**
	 * Defines the bean state as initialized
	 */
	public void initializationDone() {
		if (!this.initializationPhase) {
			throw new IllegalStateException(
					"Impossible to update bean state to \"Initialized\" since it's not in initialization state");
		}
		this.initializationPhase = false;
	}

	@Override
	public String destroyMethod() {
		return this.destroyMethod;
	}

	@Override
	public boolean isInitializingPhase() {
		return initializationPhase;
	}

	@Override
	public String initializingMethod() {
		return this.initializingMethod;
	}

	/**
	 * Sets the name of the method which should be called before the bean
	 * destruction.
	 * <p>
	 * This method must not require any arguments.
	 * 
	 * @param method
	 */
	public void setDestroyMethod(String method) {
		this.destroyMethod = method;
	}

	/**
	 * Sets the name of the method which should be called after the bean
	 * initialization and property set.
	 * <p>
	 * This method must not require any arguments.
	 * 
	 * @param method
	 */
	public void setInitializingMethod(String method) {
		this.initializingMethod = method;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == this)
			return true;
		if (obj == null)
			return false;
		if (obj instanceof BeanDefinition) {
			BeanDefinition other = (BeanDefinition) obj;

			if (!this.name.equals(other.getName()))
				return false;

			if (!this.type.equals(other.getType()))
				return false;

			if (!this.instantiatingClass.equals(other.getInstantiatingClass()))
				return false;

			if (!this.scopeName.equals(other.getScope()))
				return false;

			return true;
		}
		return false;

	}
}
